package edu.pitt.dbmi.odie.gapp.gwt.server.analysis;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.ServletContext;

import com.allen_sauer.gwt.log.client.Log;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gdata.client.docs.DocsService;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.data.docs.DocumentListFeed;
import com.google.gdata.util.ServiceException;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import edu.pitt.dbmi.odie.gapp.gwt.client.analysis.ODIE_AnalysisService;
import edu.pitt.dbmi.odie.gapp.gwt.client.dao.ODIE_DaoAnalysis;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Analysis;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Annotation;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Cls;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Document;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_User;
import edu.pitt.dbmi.odie.gapp.gwt.server.navigator.ODIE_SelectionCacher;
import edu.pitt.dbmi.odie.gapp.gwt.server.user.ODIE_ServerSideLoginInfo;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_DocsServiceFetcher;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_DocumentEntitySAXParser;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_GDataDocsService;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_GDataDocument;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.marginizer.ODIE_Marginizer;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.oba.ODIE_ObaAnnotationBean;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.oba.ODIE_ObaCoder;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class ODIE_AnalysisServiceImpl extends RemoteServiceServlet implements
		ODIE_AnalysisService {

	private static final Logger log = Logger
			.getLogger(ODIE_AnalysisServiceImpl.class.getName());

	private static final ODIE_Marginizer marginizer = new ODIE_Marginizer();

	private static ODIE_ObaCoder coder = null;

	private ODIE_GDataDocsService docsService = null;

	private ODIE_SelectionCacher selectionCacher = null;

	/*
	 * newInstance
	 * 
	 * Entry Point for the analysis service. A new ODIE_Analysis is persisted in
	 * state STAGING
	 * 
	 * (non-Javadoc)
	 * 
	 * @see
	 * edu.pitt.dbmi.odie.gapp.gwt.client.analysis.ODIE_AnalysisService#newInstance
	 * (java.lang.String, java.lang.String)
	 */
	public ODIE_DaoAnalysis newInstance(String directoryHandle, String title) {
		ODIE_DaoAnalysis result = null;
		log.info("Creating new Analysis for " + directoryHandle + " called "
				+ title);
		if (establishSupportModules()) {
			Stack<Object> objsStack = new Stack<Object>();
			String odieUserId = fetchCurrentUser(this.getServletContext());
			Date creationDate = new Date();
			Date modifiedDate = creationDate;
			title = timeStampTitle(title, creationDate);
			ODIE_Analysis presistedAnalysis = new ODIE_Analysis();
			presistedAnalysis.setDirectoryHandle(directoryHandle);
			presistedAnalysis.setTitle(title);
			presistedAnalysis.setAnalysisStatus("STAGING");
			presistedAnalysis.setUserId(odieUserId);
			presistedAnalysis.setCreationDate(creationDate);
			presistedAnalysis.setModifiedDate(modifiedDate);
			presistedAnalysis.setUnitsCompleted(0);
			presistedAnalysis.setUnitsRequested(0);
			presistedAnalysis.setProcessingUnitName("");
			objsStack.push(presistedAnalysis);
			persistObjects(objsStack);
			presistedAnalysis = this.selectionCacher
					.fetchAnalysisById(presistedAnalysis.getOdieId());
			result = presistedAnalysis.convertToClientSideRecord();
		}
		log.info("Sucessfully created analysis");
		return result;
	}

	/*
	 * process
	 * 
	 * Entry Point for the analysis service. Main controller for status
	 * transition
	 */
	public ODIE_DaoAnalysis process(ODIE_DaoAnalysis daoAnalysis) {
		ODIE_DaoAnalysis result = null;
		log.info("Entering process analysis with id = "
				+ daoAnalysis.getOdieId());
		if (establishSupportModules()) {
			ODIE_Analysis persistentAnalysis = this.selectionCacher
					.fetchAnalysisById(daoAnalysis.getOdieId());
			log.info("Fetched persistentAnalysis with odieId = "
					+ persistentAnalysis.getOdieId() + " and analysisStatus = "
					+ persistentAnalysis.getAnalysisStatus());
			if (persistentAnalysis.getAnalysisStatus().equals("STAGING")) {
				processStage(daoAnalysis, persistentAnalysis);
			} else if (persistentAnalysis.getAnalysisStatus().equals("CODING")) {
				processAutoCode(persistentAnalysis);
			}
			timeStampAnalysisModification(persistentAnalysis);
			result = persistentAnalysis.convertToClientSideRecord();
			log.info("process: Returning analysisDAO in state ==> "
					+ result.getAnalysisStatus());
		}
		log.info("Returning from process with id = " + daoAnalysis.getOdieId());
		return result;
	}

	private void timeStampAnalysisModification(ODIE_Analysis persistedAnalysis) {
		startTransaction();
		persistedAnalysis.setModifiedDate(new Date());
		commitTransaction();
	}

	/*
	 * select
	 * 
	 * Entry Point for the analysis service. Selects a new analysis
	 */
	public ODIE_DaoAnalysis select(ODIE_DaoAnalysis odieAnalysis) {
		ODIE_DaoAnalysis result = null;
		if (establishSupportModules()) {
			this.selectionCacher.deSelectAnalysis();
			this.selectionCacher.selectAnalysisById(odieAnalysis.getOdieId());
			result = this.selectionCacher.fetchAnalysisDao();
		}
		return result;
	}

	public void delete(ODIE_DaoAnalysis odieAnalysis) {

		log.info("Deleting all db entities except ODIE_User");

		int totalDeletedOnThisCall = 0;
		int maxDeletionsPerCall = 500;

		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();
		com.google.appengine.api.datastore.Query q = new com.google.appengine.api.datastore.Query(
				ODIE_Annotation.class.getSimpleName());
		ArrayList<Key> keys = new ArrayList<Key>();
		for (Entity taskEntity : datastore.prepare(q).asIterable()) {
			if (totalDeletedOnThisCall < maxDeletionsPerCall) {
				keys.add(taskEntity.getKey());
				totalDeletedOnThisCall++;
			}

		}
		datastore.delete(keys);

		if (totalDeletedOnThisCall < maxDeletionsPerCall) {
			q = new com.google.appengine.api.datastore.Query(ODIE_Cls.class
					.getSimpleName());
			keys = new ArrayList<Key>();
			for (Entity taskEntity : datastore.prepare(q).asIterable()) {
				if (totalDeletedOnThisCall < maxDeletionsPerCall) {
					keys.add(taskEntity.getKey());
					totalDeletedOnThisCall++;
				}
			}
			datastore.delete(keys);
		}

		if (totalDeletedOnThisCall < maxDeletionsPerCall) {
			q = new com.google.appengine.api.datastore.Query(
					ODIE_Document.class.getSimpleName());
			keys = new ArrayList<Key>();
			for (Entity taskEntity : datastore.prepare(q).asIterable()) {
				if (totalDeletedOnThisCall < maxDeletionsPerCall) {
					keys.add(taskEntity.getKey());
					totalDeletedOnThisCall++;
				}
			}
			datastore.delete(keys);
		}

		if (totalDeletedOnThisCall < maxDeletionsPerCall) {
			q = new com.google.appengine.api.datastore.Query(
					ODIE_Analysis.class.getSimpleName());
			keys = new ArrayList<Key>();
			for (Entity taskEntity : datastore.prepare(q).asIterable()) {
				if (totalDeletedOnThisCall < maxDeletionsPerCall) {
					keys.add(taskEntity.getKey());
					totalDeletedOnThisCall++;
				}
			}
			datastore.delete(keys);
		}

		log.info("Exiting the delete method");
	}

	public void deleteHangs(ODIE_DaoAnalysis odieAnalysis) {
		if (establishSupportModules()) {
			ODIE_Analysis persistentAnalysis = this.selectionCacher
					.fetchAnalysisById(odieAnalysis.getOdieId());
			ODIE_Analysis currentAnalysis = this.selectionCacher
					.getCurrentAnalysis();
			if (currentAnalysis != null
					&& currentAnalysis.getOdieId().equals(
							persistentAnalysis.getOdieId())) {
				this.selectionCacher.clearSelection();
			}

			//
			// Delete all Clses for this Analysis
			// 
			Stack<Object> objsToDelete = new Stack<Object>();
			for (ODIE_Cls persistedCls : this.selectionCacher
					.fetchClsesForAnalysis(odieAnalysis.getOdieId())) {
				objsToDelete.push(persistedCls);
			}
			deleteObjects(objsToDelete);
			objsToDelete.clear();

			//
			// Delete all Annotations for this Analysis
			//
			for (ODIE_Annotation persistedAnnotation : this.selectionCacher
					.fetchAnnotationsForAnalysis(odieAnalysis.getOdieId())) {
				objsToDelete.push(persistedAnnotation);
			}
			deleteObjects(objsToDelete);
			objsToDelete.clear();

			//
			// Delete all Documents for this Analysis
			//
			for (ODIE_Document persistedDocument : this.selectionCacher
					.fetchDocumentsForAnalysis(odieAnalysis.getOdieId())) {
				objsToDelete.push(persistedDocument);
			}
			deleteObjects(objsToDelete);
			objsToDelete.clear();

			//
			// Delete the Analysis itself
			//
			objsToDelete.push(persistentAnalysis);
			deleteObjects(objsToDelete);
			objsToDelete.clear();
		}

	}

	private void startTransaction() {
		this.selectionCacher.getPersistenceManager().currentTransaction()
				.begin();
	}

	private void commitTransaction() {
		this.selectionCacher.getPersistenceManager().currentTransaction()
				.commit();
	}

	public void processStage(ODIE_DaoAnalysis daoAnalysis,
			ODIE_Analysis persistedAnalysis) {
		List<ODIE_GDataDocument> documentObjs = pullDocumentsFromDirectory(persistedAnalysis);
		persistDocumentsForCoding(persistedAnalysis, documentObjs);
		persistCodingParametersFromDAO(daoAnalysis, persistedAnalysis);
		seedObaCoderFromDAO(daoAnalysis);
		xFerAnalysisStatus(persistedAnalysis, "CODING");
	}

	private void seedObaCoderFromDAO(ODIE_DaoAnalysis daoAnalysis) {
		if (coder != null) {
			coder.setLongestOnly(daoAnalysis.isLongestOnly());
			coder.setWholeWordOnly(daoAnalysis.isWholeWordOnly());
			// coder.setStopWords(daoAnalysis.getStopWords()) ;
			// coder.setWithDefaultStopWords(daoAnalysis.isWithDefaultStopWords())
			// ;
			// coder.setMinTermSize(daoAnalysis.getMinTermSize()) ;
			coder.setScored(daoAnalysis.isScored());

			if (daoAnalysis.getLocalOntologyIDs() != null) {
				coder.setLocalOntologyIDs(Arrays.asList(daoAnalysis
						.getLocalOntologyIDs().split(",")));
			}
			if (daoAnalysis.getLocalSemanticTypeIDs() != null) {
				coder.setLocalSemanticTypeIDs(Arrays.asList(daoAnalysis
						.getLocalSemanticTypeIDs().split(",")));
			}

			coder.setLevelMin(daoAnalysis.getLevelMin());
			coder.setLevelMax(daoAnalysis.getLevelMax());
			// coder.setActivateMappings(daoAnalysis.isActivateMappings()) ;

			if (daoAnalysis.getMappingTypes() != null) {
				coder.setMappingTypes(Arrays.asList(daoAnalysis
						.getMappingTypes().split(",")));
			}
		}
	}

	private void persistCodingParametersFromDAO(ODIE_DaoAnalysis daoAnalysis,
			ODIE_Analysis persistedAnalysis) {
		persistedAnalysis.setLongestOnly(daoAnalysis.isLongestOnly());
		persistedAnalysis.setWholeWordOnly(daoAnalysis.isWholeWordOnly());
		persistedAnalysis.setStopWords(daoAnalysis.getStopWords());
		persistedAnalysis.setWithDefaultStopWords(daoAnalysis
				.isWithDefaultStopWords());
		persistedAnalysis.setMinTermSize(daoAnalysis.getMinTermSize());
		persistedAnalysis.setScored(daoAnalysis.isScored());
		persistedAnalysis
				.setLocalOntologyIDs(daoAnalysis.getLocalOntologyIDs());
		persistedAnalysis.setLocalSemanticTypeIDs(daoAnalysis
				.getLocalSemanticTypeIDs());
		persistedAnalysis.setLevelMin(daoAnalysis.getLevelMin());
		persistedAnalysis.setLevelMax(daoAnalysis.getLevelMax());
		persistedAnalysis.setActivateMappings(daoAnalysis.isActivateMappings());
		persistedAnalysis.setMappingTypes(daoAnalysis.getMappingTypes());
	}

	private ODIE_Document getDocumentToCode(ODIE_Analysis persistedAnalysis) {
		ODIE_Document persistedDocument = this.selectionCacher
				.fetchDocumentWithStatus(persistedAnalysis, "CODING");
		if (persistedDocument == null) {
			persistedDocument = this.selectionCacher.fetchDocumentWithStatus(
					persistedAnalysis, "STAGING");
			if (persistedDocument != null) {
				persistedDocument = xFerDocumentStatusToCoding(
						persistedAnalysis, persistedDocument);
			}
		}
		return persistedDocument;
	}

	private ODIE_Document xFerDocumentStatusToCoding(
			ODIE_Analysis persistedAnalysis, ODIE_Document persistedDocument) {

		persistedDocument.setDocumentStatus("CODING");
		String documentId = persistedDocument.getRawId();
		String documentText = this.docsService.pullDocumentContent(docsService
				.stripLeadingDocumentType(documentId));
		marginizer.setInputFileAsString(documentText);
		marginizer.execute();
		documentText = marginizer.getOutputFileAsString();
		coder.setDocumentText(documentText);
		persistedDocument.setCurrentAnnotationOffset(0);
		persistedDocument.setCurrentLineOffset(0);
		persistedDocument.setNumberOfLines(coder.calculateNumberOfLines());
		startTransaction();
		this.selectionCacher.getPersistenceManager().refresh(persistedDocument) ;
		commitTransaction();
		
		persistedAnalysis.setSubUnitsRequested(persistedDocument
				.getNumberOfLines());
		persistedAnalysis.setSubUnitsCompleted(0);
		persistedAnalysis.setProcessingUnitName(persistedDocument.getTitle());
		startTransaction();
		this.selectionCacher.getPersistenceManager().refresh(persistedAnalysis) ;
		commitTransaction();

		persistedDocument = this.selectionCacher.fetchDocumentWithStatus(
				persistedAnalysis, "CODING");
		
		return persistedDocument;
	}

	private String getLinesProcessedDiagnostic(ODIE_Document persistedDocument) {
		StringBuffer sb = new StringBuffer(0);
		sb.append("Sentence ");
		sb.append(persistedDocument.getCurrentLineOffset());
		sb.append(" of ");
		sb.append(persistedDocument.getNumberOfLines());
		return sb.toString();
	}

	public ODIE_Analysis processAutoCode(ODIE_Analysis persistedAnalysis) {
		Log.debug("Calling autocode with directoryHandle ==>"
				+ persistedAnalysis.getDirectoryHandle());
		ODIE_Document persistedDocument = getDocumentToCode(persistedAnalysis);
		if (persistedDocument != null) {
			String documentId = persistedDocument.getRawId();
			String documentText = docsService.pullDocumentContent(docsService
					.stripLeadingDocumentType(documentId));
			marginizer.setInputFileAsString(documentText);
			marginizer.execute();
			documentText = marginizer.getOutputFileAsString();
			coder.setDocumentText(documentText);
			coder.execute(persistedDocument.getCurrentLineOffset(),
					persistedDocument.getCurrentAnnotationOffset());
			ArrayList<ODIE_ObaAnnotationBean> documentAnnotations = new ArrayList<ODIE_ObaAnnotationBean>();
			documentAnnotations.addAll(coder.getAnnotations());
			cacheDocumentAnnotations(persistedAnalysis, persistedDocument,
					documentAnnotations);
			persistedDocument
					.setCurrentLineOffset(coder.getCurrentLineOffset());
			persistedDocument.setCurrentAnnotationOffset(coder
					.getCurrentAnnotationOffset());
			int currentLineOffset = persistedDocument.getCurrentLineOffset();
			int numberOfLines = persistedDocument.getNumberOfLines();
			if (currentLineOffset >= numberOfLines) {
				persistedDocument.setDocumentStatus("IDLING");
			}
			startTransaction();
			this.selectionCacher.getPersistenceManager().makePersistent(persistedDocument) ;
			commitTransaction();
			
			if (currentLineOffset >= numberOfLines) {
				persistedAnalysis.setUnitsCompleted(persistedAnalysis
						.getUnitsCompleted() + 1);
			}
			persistedAnalysis.setSubUnitsCompleted(persistedAnalysis
					.getSubUnitsCompleted()
					+ coder.getBatchSize());
			persistedAnalysis
					.setProcessingSubUnitName(getLinesProcessedDiagnostic(persistedDocument));
			startTransaction();
			this.selectionCacher.getPersistenceManager().makePersistent(persistedAnalysis) ;
			commitTransaction();
		} else {
			xFerAnalysisStatus(persistedAnalysis, "IDLING");
		}

		return persistedAnalysis;
	}

	private void xFerAnalysisStatus(ODIE_Analysis persistedAnalysis,
			String analysisStatus) {
		startTransaction();
		persistedAnalysis.setAnalysisStatus(analysisStatus);
		commitTransaction();
	}

	private List<ODIE_GDataDocument> pullDocumentsFromDirectory(
			ODIE_Analysis odieAnalysis) {
		ArrayList<ODIE_GDataDocument> documentObjs = null;
		try {
			//
			// Use SAXParser to build objects from directory feed response.
			//
			String folderId = this.extractIdFromUrl(odieAnalysis
					.getDirectoryHandle());
			Log.debug("Staging: Got folder named " + folderId);
			String urlName = "http://docs.google.com/feeds/default/private/full/"
					+ folderId + "/contents";
			log.info("Opening documents in folder " + urlName);
			URL url = new URL(urlName);
			DocumentListFeed feed = docsService.getDocsService().getFeed(url,
					DocumentListFeed.class);
			documentObjs = new ArrayList<ODIE_GDataDocument>();
			boolean isFirst = true;
			for (DocumentListEntry entry : feed.getEntries()) {
				String documentXml = docsService.extractDocumentXml(entry);
				if (isFirst) {
					log.info(documentXml);
					isFirst = false;
				}
				ODIE_DocumentEntitySAXParser.getInstance().parseDocument(
						documentXml);
				documentObjs.add(ODIE_DocumentEntitySAXParser.getInstance()
						.getOdieGDataDocument());
			}
			if (isFirst) {
				log.info("No documents read.");
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ServiceException e) {
			e.printStackTrace();
		}
		return documentObjs;
	}

	private void persistDocumentsForCoding(ODIE_Analysis persistedAnalysis,
			List<ODIE_GDataDocument> documentObjs) {
		//
		// Persist the ODIE_Document objects and update ODIE_Analysis
		//
		persistedAnalysis.setUnitsRequested(documentObjs.size());
		persistedAnalysis.setUnitsCompleted(0);
		Stack<Object> objsStack = new Stack<Object>();
		boolean isSelected = true;
		for (Iterator<ODIE_GDataDocument> iterator = documentObjs.iterator(); iterator
				.hasNext();) {
			ODIE_GDataDocument odieGDataDocument = iterator.next();
			ODIE_Document persistedDocument = new ODIE_Document();
			persistedDocument.setOdieAnalysisId(persistedAnalysis.getOdieId());
			persistedDocument.setTitle(odieGDataDocument.getTitle());
			persistedDocument.setAtomId(odieGDataDocument.getAtomId());
			persistedDocument.setRawId(odieGDataDocument.getRawId());
			persistedDocument.setDocumentStatus("STAGING");
			persistedDocument.setSelected(isSelected);
			isSelected = false;
			objsStack.push(persistedDocument);
		}
		persistObjects(objsStack);
	}

	private String fetchCurrentUser() {
		UserService userService = UserServiceFactory.getUserService();
		User user = userService.getCurrentUser();
		ODIE_User odieUser = this.selectionCacher.fetchOrCreateUser(user
				.getEmail(), user.getNickname());
		String odieUserId = odieUser.getOdieId();
		return odieUserId;
	}

	private String fetchCurrentUser(ServletContext servletContext) {
		String odieUserId = null;
		ODIE_ServerSideLoginInfo serverSideLoginInfo = (ODIE_ServerSideLoginInfo) servletContext
				.getAttribute(ODIE_ServerSideLoginInfo.class.getName());
		if (serverSideLoginInfo != null) {
			ODIE_User odieUser = this.selectionCacher.fetchOrCreateUser(
					serverSideLoginInfo.getEmailAddress(), serverSideLoginInfo
							.getNickName());
			odieUserId = odieUser.getOdieId();
		}
		return odieUserId;
	}

	private boolean establishSupportModules() {
		return establishDocsServiceFetcher() && establishSelectionCacher()
				&& establishObaCoder();
	}

	private boolean establishDocsServiceFetcher() {
		this.docsService = ODIE_DocsServiceFetcher.fetchDocsService(this);
		return this.docsService != null;
	}

	private boolean establishSelectionCacher() {
		this.selectionCacher = ODIE_SelectionCacher
				.fetchOdieSelectionCacher(this);
		return this.selectionCacher != null;
	}

	private boolean establishObaCoder() {
		if (coder == null) {
			coder = new ODIE_ObaCoder();
			coder.initialize(this.selectionCacher.getLoginInfo());
			coder.setBatchSize(5);
		}
		return coder != null;
	}

	private void cacheDocumentAnnotations(ODIE_Analysis persistedAnalysis,
			ODIE_Document persistedDocument,
			ArrayList<ODIE_ObaAnnotationBean> annotationObjs) {
		Stack<Object> objsStack = new Stack<Object>();
		boolean isSelected = false;
		if (annotationObjs.size() > 0) {
			for (Iterator<ODIE_ObaAnnotationBean> annotationIterator = annotationObjs
					.iterator(); annotationIterator.hasNext();) {
				ODIE_ObaAnnotationBean annotation = annotationIterator.next();
				ODIE_Annotation odieAnnotation = new ODIE_Annotation();
				odieAnnotation.setOdieAnalysisId(persistedAnalysis.getOdieId());
				odieAnnotation.setOdieDocumentId(persistedDocument.getOdieId());
				odieAnnotation.setSPos(annotation.getSPos());
				odieAnnotation.setEPos(annotation.getEPos());
				ODIE_Cls odieCls = fetchOrCreateCls(this.selectionCacher
						.getPersistenceManager(), annotation, persistedAnalysis);
				odieAnnotation.setOdieClsId(odieCls.getOdieId());
				odieAnnotation.setOntologyName(odieCls.getOntologyName());
				odieAnnotation.setClsName(odieCls.getClsName());
				odieAnnotation.setSelected(isSelected);
				isSelected = false;
				objsStack.push(odieAnnotation);
			}
		}
		persistObjects(objsStack);

	}

	private void persistObjects(Stack<Object> objsStack) {
		try {
			
			while (!objsStack.isEmpty()) {
//				startTransaction();
				this.selectionCacher.getPersistenceManager().makePersistent(
						objsStack.pop());
//				commitTransaction();
			}
			
		} catch (Exception x) {
			Log.fatal(x.getMessage());
		}
	}

	private void deleteObjects(Stack<Object> objsStack) {
		try {
			Log.debug("Deleting " + objsStack.size() + " objects.");
			while (!objsStack.isEmpty()) {
//				startTransaction();
				this.selectionCacher.getPersistenceManager().deletePersistent(
						objsStack.pop());
//				commitTransaction();
			}
			
		} catch (Exception x) {
			Log.fatal(x.getMessage());
		}
	}

	private String timeStampTitle(String title, Date creationDate) {
		String formatTemplate = "_yyyy_MM_dd_HH_mm_ss_SSS";
		SimpleDateFormat df = new SimpleDateFormat(formatTemplate);
		return title + df.format(creationDate);
	}

	// @SuppressWarnings("unchecked")
	// public void deSelectDocumentForAnalysis(String odieAnalysisId) {
	// try {
	// persistenceManager = ODIE_PMF.get().getPersistenceManager();
	// Query query = persistenceManager
	// .newQuery(ODIE_Document.class,
	// "odieAnalysisId == odieAnalysisIdParam && isSelected == isSelectedParam");
	// query
	// .declareParameters("String odieAnalysisIdParam, boolean isSelectedParam");
	// List<ODIE_Document> results = (List<ODIE_Document>) query.execute(
	// odieAnalysisId, true);
	// if (results != null && results.size() > 0) {
	// this.currentDocument = results.get(0);
	// persistenceManager.currentTransaction().begin();
	// this.currentDocument.setSelected(false);
	// persistenceManager.currentTransaction().commit();
	// this.currentDocument = null;
	// this.currentAnnotation = null;
	// this.currentCls = null ;
	// }
	// } catch (Exception x) {
	// x.printStackTrace();
	// }
	// }

	@SuppressWarnings("unchecked")
	private ODIE_Cls fetchOrCreateCls(PersistenceManager pm,
			ODIE_ObaAnnotationBean odieAnnotation, ODIE_Analysis odieAnalysis) {
		ODIE_Cls result = null;
		int searchTries = 0;
		int searchMax = 3;
		while (searchTries < searchMax) {
			try {
				Query query = pm
						.newQuery(ODIE_Cls.class,
								"cui == cuiParam && odieAnalysisId == odieAnalysisIdParam");
				query
						.declareParameters("String cuiParam, String odieAnalysisIdParam");
				List<ODIE_Cls> clses = (List<ODIE_Cls>) query.execute(
						odieAnnotation.getCui(), odieAnalysis.getOdieId());
				if (clses != null && !clses.isEmpty()) {
					for (ODIE_Cls odieCls : clses) {
						if (odieCls.getCui().equals(odieAnnotation.getCui())
								&& odieCls.getOdieAnalysisId().equals(
										odieAnalysis.getOdieId())) {
							result = odieCls;
							break;
						}
					}
				}
				if (result == null) {
					result = new ODIE_Cls();
					String ontologyName = odieAnnotation.getLocalOntologyID();
					String clsName = odieAnnotation.getPreferredName();
					String cui = odieAnnotation.getCui();
					Log.debug("Caching new Cls (" + ontologyName + ", "
							+ clsName + ", " + cui + ")");
					result.setOdieAnalysisId(odieAnalysis.getOdieId());
					result.setOntologyName(ontologyName);
					result.setClsName(clsName);
					result.setColorAsString("#FFACEC");
					result.setCui(cui);
					try {
						pm.makePersistent(result);
					} catch (Exception x) {
						x.printStackTrace();
					}
				}
				break; // We have either found the Cls or created a new one.
			} catch (com.google.appengine.api.datastore.DatastoreTimeoutException e) {
				log.info("DatastoreTimeoutException: Searching for "
						+ odieAnnotation.getCui() + " on try " + searchTries);
			} catch (Exception x) {
				log.info("Exception: Searching for " + odieAnnotation.getCui()
						+ " on try " + searchTries);
			} finally {
				searchTries++;
			}
		}

		return result;
	}

	private String extractIdFromUrl(String urlName) {
		int sPos = urlName.lastIndexOf("/");
		return urlName.substring(sPos + 1, urlName.length());
	}

	public DocumentListEntry pullEntryByFullId(DocsService docsService,
			String fullId) {
		DocumentListEntry entry = null;
		URL documentURI;
		try {
			documentURI = new URL(
					"http://docs.google.com/feeds/documents/private/full/"
							+ fullId);
			entry = docsService.getEntry(documentURI, DocumentListEntry.class);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ServiceException e) {
			e.printStackTrace();
		}
		return entry;
	}

}
